/*-------------------------------------------------------------------------
 - Copyright (c) 2024-2025 [XD-AMCC TEAM]
 - [XD-AMCC] is licensed under Mulan PSL v2.
 - You can use this software according to the terms and conditions of the Mulan PSL v2.
 - You may obtain a copy of Mulan PSL v2 at:
 -          http://license.coscl.org.cn/MulanPSL2
 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
 - OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 - TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 - See the Mulan PSL v2 for more details.
 ------------------------------------------------------------------------*/


/**
 * @brief Implement a generic tree-walk framework and
 *        an abstract tree_listener.
 *
 * In AMCC, there are some concrete listeners:
 *  - tree dumper, is defined in tree_dump.c, and
 *  - the symbol table builder, and
 *  - the IR code emitter, and ...
 *
 * @author  WXQ#XDU
 * @date    2024.11
 */

#include <stdlib.h>

#include "../include/amcc_string.h"
#include "../include/amcc_log.h"
#include "../include/tree.h"

/**
 * Helper type for calculating the functions address
 * corresponding to the access operations.
 */
typedef struct  nt_visitfunc {
    EnumNonTerminal  ntId;
    size_t   fn_enter_offset;
    size_t   fn_exit_offset;
} t_nt_visitfunc;

static t_nt_visitfunc  arr_visitfunc[] = {

    // make a triple like { NT_X, enter_X, exit_X }
    #define  GRAM_NT_DEF(nt, n1, n2) \
          { NT_##nt , \
            AMCC_OFFSETOF(t_tree_listener, enter_##nt), \
            AMCC_OFFSETOF(t_tree_listener, exit_##nt) }

    #include "../meta/_gramsymbol.inc"

    #undef   GRAM_NT_DEF

};


static int compare_nt_(const void* a, const void* b){
    const t_nt_visitfunc * pa = (const t_nt_visitfunc*)a;
    const t_nt_visitfunc * pb = (const t_nt_visitfunc*)b;
    return (int)(pa->ntId - pb->ntId);
}

static t_nt_visitfunc * find_visit_func_(EnumNonTerminal ntId) {
    t_nt_visitfunc key;
    key.ntId = ntId;
    return bsearch(&key, arr_visitfunc,
                   sizeof(arr_visitfunc) / sizeof(arr_visitfunc[0]),
                   sizeof(arr_visitfunc[0]),
                   compare_nt_
    );
}


static bool should_stop_visit_(t_tree_ptr node,
                               t_tree_listener_ptr listenerPtr)
{
    bool shouldStopVisit = listenerPtr->shouldStopVisit ;
    if (shouldStopVisit)
        return true;

    if (NULL != listenerPtr->isShouldStopVisit) {
        shouldStopVisit = listenerPtr->isShouldStopVisit(listenerPtr, node);
        if (shouldStopVisit)
            listenerPtr->shouldStopVisit = true;
    }

    return shouldStopVisit;
}


// call listener.enter_X() for non-terminal X
static void call_enterNonTerminalNode_(t_tree_ptr root,
                                       t_tree_listener_ptr listenerPtr)
{
    t_nonterminal_ptr           nt = tree_get_nonTerminal(root);
    EnumNonTerminal           ntId = nt->ntId;
    t_nt_visitfunc *       functor = find_visit_func_(ntId);
    t_treefunc_visitnode fnVisitNt = NULL;
    if (NULL != functor)  {
        fnVisitNt = AMCC_ADDRESS(t_treefunc_visitnode,
                                 listenerPtr,
                                 functor->fn_enter_offset);
    }
    if (NULL == fnVisitNt)
        fnVisitNt = listenerPtr->enterNonTerminalNode ;

    if (NULL != fnVisitNt)
        fnVisitNt(listenerPtr, root);
}


// call listener.exit_X() for non-terminal X
static void call_exitNonTerminalNode_(t_tree_ptr root,
                                      t_tree_listener_ptr listenerPtr)
{
    t_nonterminal_ptr          nt = tree_get_nonTerminal(root);
    EnumNonTerminal          ntId = nt->ntId;
    t_nt_visitfunc *       functor = find_visit_func_(ntId);
    t_treefunc_visitnode fnVisitNt = NULL;
    if ( NULL != functor )  {
        fnVisitNt = AMCC_ADDRESS(t_treefunc_visitnode,
                                 listenerPtr,
                                 functor->fn_exit_offset);
    }
    if (NULL == fnVisitNt)
        fnVisitNt = listenerPtr->exitNonTerminalNode ;

    if (NULL != fnVisitNt)
        fnVisitNt(listenerPtr, root);
}


// Visit a parent node, it SHOULD be marked by a non-terminal
static void visit_nonterminal_node_(t_tree_ptr root,
                                    t_tree_listener_ptr listenerPtr)
{
    if (NULL != listenerPtr->enterEveryNode)
        listenerPtr->enterEveryNode(listenerPtr, root);
    call_enterNonTerminalNode_(root, listenerPtr);
    listenerPtr->depthCounter += 1;

    // visit all children
    t_list_iterator  itr;
    LIST_ITERATOR_INIT ( &itr, tree_get_children(root) );
    t_list_node_ptr node = LIST_ITERATOR_BEGIN ( &itr );
    t_list_node_ptr end  = LIST_ITERATOR_END   ( &itr );
    while ( node != end ) {
        t_tree_ptr  child = (t_tree_ptr)list_node_get_value(node);

        if (should_stop_visit_(child, listenerPtr) )
            break;

        tree_walk( child, listenerPtr );
        node = LIST_ITERATOR_NEXT ( &itr );
    }

    listenerPtr->depthCounter -= 1;
    call_exitNonTerminalNode_(root, listenerPtr);
    if (NULL != listenerPtr->exitEveryNode)
        listenerPtr->exitEveryNode(listenerPtr, root);
}

// Visit a leaf node marked by a token
static void visit_terminal_node_(t_tree_ptr  leaf,
                                 t_tree_listener_ptr listenerPtr)
{
    if (NULL != listenerPtr->enterEveryNode)
        listenerPtr->enterEveryNode    (listenerPtr, leaf);
    if (NULL != listenerPtr->visitTerminalNode)
        listenerPtr->visitTerminalNode (listenerPtr, leaf);
    if (NULL != listenerPtr->exitEveryNode)
        listenerPtr->exitEveryNode     (listenerPtr, leaf);
}

// Visit a leaf node marked by EPSILON
static void visit_epsilon_node_(t_tree_ptr  leaf,
                                t_tree_listener_ptr listenerPtr)
{
    if (NULL != listenerPtr->enterEveryNode)
        listenerPtr->enterEveryNode    (listenerPtr, leaf);
    if (NULL != listenerPtr->visitEpsilonNode)
        listenerPtr->visitEpsilonNode  (listenerPtr, leaf);
    if (NULL != listenerPtr->exitEveryNode)
        listenerPtr->exitEveryNode     (listenerPtr, leaf);
}



void tree_walk(t_tree_ptr  tree, t_tree_listener_ptr listenerPtr) {
    if (NULL == tree || NULL == listenerPtr)
        return;

    if (should_stop_visit_(tree, listenerPtr) )
        return;

    if /**/ ( tree_is_nonTerminal(tree) )
        visit_nonterminal_node_(tree, listenerPtr);
    else if ( tree_is_terminal(tree) )
        visit_terminal_node_(tree, listenerPtr);
    else // (tree_is_epsilon(tree))
        visit_epsilon_node_(tree, listenerPtr);
}


void tree_listener_init0(t_tree_listener_ptr pThis, size_t sizeOfListener) {
    size_t  baseSize = sizeof(t_tree_listener);
    if (sizeOfListener <= baseSize)
        sizeOfListener = baseSize;
    memset(pThis, 0, sizeOfListener);
    pThis->depthCounter         = 1;
    pThis->shouldStopVisit      = false;

    // All access operations should be defined by derived classes.
    // By default, they are NULL, at here, indicating do-nothing.
}
